// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ReferenceTable.cs" company="dimamartovoi">
//   Apache License
//   Version 2.0, January 2004
//   http://www.apache.org/licenses
// </copyright>
// <summary>
//   Defines the ReferenceTable type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection.Emit;

namespace ActorModel.Evolution.SemanticAnalyzing
{
    /// <summary>
    ///     The reference table.
    /// </summary>
    public class ReferenceTable
    {
        #region Constants

        /// <summary>
        ///     The empty.
        /// </summary>
        public const ReferenceTable Empty = null;

        #endregion

        #region Fields

        /// <summary>
        ///     The locals.
        /// </summary>
        private readonly ICollection<LocalVariable> locals = new Collection<LocalVariable>();

        /// <summary>
        ///     The uppers.
        /// </summary>
        private readonly ICollection<LocalVariable> uppers = new Collection<LocalVariable>();

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets the variables.
        /// </summary>
        public IEnumerable<LocalVariable> Variables
        {
            get
            {
                var variables = new Collection<LocalVariable>();
                foreach (LocalVariable local in this.locals)
                {
                    variables.Add(local);
                }

                foreach (LocalVariable upper in this.uppers)
                {
                    if (variables.Any(variable => variable.Id == upper.Id))
                    {
                        continue;
                    }

                    variables.Add(upper);
                }

                return variables;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The add local.
        /// </summary>
        /// <param name="variable">
        /// The variable.
        /// </param>
        public void AddLocal(LocalVariable variable)
        {
            this.locals.Add(variable);
        }

        /// <summary>
        /// The is defined.
        /// </summary>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public bool IsDefined(string id)
        {
            IEnumerable<LocalVariable> variables = this.Variables;
            return variables.Any(variable => variable.Id == id);
        }

        /// <summary>
        /// The is defined local.
        /// </summary>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public bool IsDefinedLocal(string id)
        {
            return this.locals.Any(variable => variable.Id == id);
        }

        /// <summary>
        /// The merge with.
        /// </summary>
        /// <param name="table">
        /// The table.
        /// </param>
        public void MergeWith(ReferenceTable table)
        {
            foreach (LocalVariable variable in table.Variables)
            {
                this.uppers.Add(variable);
            }
        }

        /// <summary>
        /// The resolve builder.
        /// </summary>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <returns>
        /// The <see cref="LocalBuilder"/>.
        /// </returns>
        public LocalBuilder ResolveBuilder(string id)
        {
            LocalVariable local = this.locals.SingleOrDefault(varialbe => varialbe.Id == id);
            if (local == default(LocalVariable))
            {
                LocalVariable upper = this.uppers.SingleOrDefault(varialbe => varialbe.Id == id);
                return upper != null ? upper.Builder : default(LocalBuilder);
            }

            return local.Builder;
        }

        #endregion
    }
}